package com.jl15988.pguard;

import com.jl15988.pguard.atomic.AtomicReferenceH;
import com.jl15988.pguard.enums.ProcessRestartStrategy;
import com.jl15988.pguard.enums.ProcessStatus;
import com.jl15988.pguard.function.ProcessStatusCallback;
import com.jl15988.pguard.function.ProcessStatusChangeCallback;
import com.jl15988.pguard.model.ProcessConfig;
import com.jl15988.pguard.model.ProcessInfo;
import com.jl15988.pguard.platform.PlatformProcessInfo;
import com.jl15988.pguard.platform.ProcessPlatform;
import com.jl15988.pguard.util.ProcessUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.PreDestroy;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 被管理的进程类
 * <p>
 * 该类负责管理单个进程的生命周期，提供进程的启动、停止、重启、暂停和恢复等功能。
 * 支持进程的状态监控、资源使用统计、输出捕获等功能。主要特性包括：
 * - 支持Windows和Linux系统
 * - 提供进程状态监控
 * - 支持进程重启策略
 * - 支持端口占用检测
 * - 提供标准输出和错误输出的回调处理
 * </p>
 *
 * @author jl15988
 * @since 1.0.0
 */
@Slf4j
@Getter
public class ManagedProcess {
    /**
     * 进程配置信息
     * 包含进程的启动命令、工作目录、环境变量等配置
     */
    private final ProcessConfig config;

    /**
     * 进程实例
     * Java进程对象，用于控制和监控实际运行的进程
     */
    private Process process;

    /**
     * 进程状态
     * 使用原子引用确保线程安全的状态访问和修改
     */
    private final AtomicReferenceH<ProcessStatus> status = new AtomicReferenceH<>(ProcessStatus.STOPPED);

    /**
     * 重启计数器
     * 记录进程重启的次数，用于实现重启策略
     */
    private final AtomicInteger restartCount = new AtomicInteger(0);

    /**
     * 标准输出监控线程
     * 用于实时捕获进程的标准输出
     */
    private Thread outputThread;

    /**
     * 错误输出监控线程
     * 用于实时捕获进程的错误输出
     */
    private Thread errorThread;

    /**
     * 进程启动时间
     * 记录进程最初启动的时间点
     */
    private LocalDateTime startTime;

    /**
     * 最后重启时间
     * 记录进程最近一次重启的时间点
     */
    private final AtomicReference<LocalDateTime> lastRestartTime = new AtomicReference<>();

    /**
     * 监控线程池
     * 用于执行进程监控任务
     */
    private final ScheduledExecutorService monitorExecutor;

    /**
     * 进程监控任务
     * 用于定期检查进程状态
     */
    private ScheduledFuture<?> monitorFuture;

    /**
     * 真正运行程序的进程ID
     */
    private Long realPid = null;

    /**
     * 构造函数
     * <p>
     * 创建一个新的被管理进程实例。
     * 初始化进程配置，但不会立即启动进程。
     * </p>
     *
     * @param config 进程配置信息
     */
    public ManagedProcess(ProcessConfig config) {
        this.config = config;
        // 设置进程状态回调
        status.setConsumer(processStatus -> {
            ProcessStatusChangeCallback statusChangeCallback = config.getStatusChangeCallback();
            if (statusChangeCallback != null) {
                statusChangeCallback.onStatusChange(processStatus);
            }
        });
        // 创建监控线程池
        this.monitorExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread t = new Thread(r, "ProcessMonitor-" + config.getProcessId());
            t.setDaemon(true);
            return t;
        });
        // 自动启动进程
        if (config.isAutoStart()) {
            start();
        }
    }

    /**
     * 启动进程
     * <p>
     * 尝试启动配置的进程。如果进程已经在运行，则直接返回。
     * 启动过程包括以下步骤：
     * 1. 检查进程状态
     * 2. 检查端口占用（如果配置了端口）
     * 3. 创建并启动进程
     * 4. 启动输出监控线程
     * </p>
     * <p>
     * 特殊情况处理：
     * - 如果配置了端口且端口被占用，会尝试监听占用该端口的已存在进程
     * - 根据操作系统类型使用不同的命令启动进程
     * - 支持工作目录和环境变量的配置
     * </p>
     *
     * @return 是否启动成功
     */
    public synchronized boolean start() {
        if (status.get() == ProcessStatus.RUNNING) {
            log.warn("进程[{}]已经在运行中", config.getProcessId());
            return true;
        }

        return this.doStart();
    }

    private boolean doStart() {
        long pid = getPid();

        if (config.getPort() != null) {
            if (ProcessUtil.testPortHealth(config.getPort())) {
                // 如果端口被占用，尝试监听占用该端口的已存在进程
                realPid = getPlatForm().findProcessIdByPort(config.getPort());
                boolean isProcessExist = realPid != -1 && getPlatForm().isProcessExist(realPid);
                if (isProcessExist) {
                    log.info("端口[{}]已被进程[{}]占用，开始监听该进程", config.getPort(), realPid);
                    try {
                        process = getPlatForm().monitorProcess(pid);
                        startTime = LocalDateTime.now();
                        status.set(ProcessStatus.RUNNING);
                        startOutputMonitor();
                        if (config.isEnableMonitoring()) {
                            startMonitoring();
                        }
                        return true;
                    } catch (Exception e) {
                        log.error("监听已存在进程[{}]失败", pid, e);
                    }
                }
                log.warn("端口[{}]已被占用，但无法监听占用进程，进程[{}]无法启动", config.getPort(), config.getProcessId());
                return false;
            }
        }

        try {
            process = getPlatForm().createProcess(
                    config.getCommand(),
                    config.getWorkingDirectory(),
                    config.getEnvironmentVariables()
            );
            startTime = LocalDateTime.now();
            status.set(ProcessStatus.RUNNING);
            startOutputMonitor();
            if (config.isEnableMonitoring()) {
                startMonitoring();
            }
            return true;
        } catch (Exception e) {
            log.error("启动进程[{}]失败", config.getProcessId(), e);
            status.set(ProcessStatus.ERROR);
            handleProcessFailure();
            return false;
        }
    }

    /**
     * 停止进程
     * <p>
     * 尝试停止正在运行的进程。如果进程已经停止，则直接返回。
     * 停止过程包括以下步骤：
     * 1. 检查进程状态
     * 2. 尝试正常终止进程
     * 3. 如果正常终止失败，则强制终止
     * 4. 停止输出监控线程
     * 5. 重置进程状态和计数器
     * </p>
     * <p>
     * 进程终止策略：
     * - 首先尝试通过Process.destroy()方法正常终止
     * - 等待最多5秒钟让进程正常终止
     * - 如果超时，则通过Process.destroyForcibly()强制终止
     * </p>
     *
     * @param resetCount 是否重置重启计数器
     */
    private synchronized void stop(boolean resetCount) {
        if (status.get() == ProcessStatus.STOPPED) {
            return;
        }

        stopMonitoring();
        if (process != null) {
            process.destroy();
            try {
                if (!process.waitFor(5, TimeUnit.SECONDS)) {
                    process.destroyForcibly();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("等待进程[{}]停止时被中断", config.getProcessId(), e);
            }
        }

        stopOutputMonitor();
        stopPortProcess();
        status.set(ProcessStatus.STOPPED);
        if (resetCount) {
            restartCount.set(0);
        }
        startTime = null;
    }

    /**
     * 停止进程（手动停止，会重置重启计数器）
     */
    public synchronized void stop() {
        stop(true);
    }

    /**
     * 重启进程
     * <p>
     * 重启正在运行的进程。重启过程包括以下步骤：
     * 1. 停止当前进程
     * 2. 等待配置的重启间隔时间
     * 3. 重新启动进程
     * 4. 更新重启时间记录
     * </p>
     * <p>
     * 重启策略：
     * - 遵循配置的重启间隔时间
     * - 记录重启时间以供监控
     * - 如果重启过程中被中断，确保线程中断状态得到正确处理
     * </p>
     *
     * @param resetCount 是否重置重启计数器
     * @return 是否重启成功
     */
    private synchronized boolean restart(boolean resetCount) {
        stop(resetCount);
        try {
            Thread.sleep(config.getRestartIntervalMs());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        lastRestartTime.set(LocalDateTime.now());
        return start();
    }

    /**
     * 手动重启进程（会重置重启计数器）
     *
     * @return 是否重启成功
     */
    public synchronized boolean restart() {
        return restart(true);
    }

    /**
     * 启动进程监控
     */
    private void startMonitoring() {
        if (config.isEnableMonitoring() && monitorFuture == null) {
            monitorFuture = monitorExecutor.scheduleAtFixedRate(() -> {
                try {
                    updateStatus();
                } catch (Exception e) {
                    log.error("进程监控异常", e);
                }
            }, 0, config.getMonitoringIntervalMs(), TimeUnit.MILLISECONDS);
        }
    }

    /**
     * 更新进程状态
     */
    public void updateStatus() {
        long pid = getPid();
        boolean processStatus = getPlatForm().isProcessExist(pid);
        boolean portStatus = config.getPort() != null && !ProcessUtil.testPortHealth(config.getPort());
        if (!processStatus || !portStatus) {
            handleProcessExit();
        }
        ProcessStatusCallback statusCallback = config.getStatusCallback();
        if (statusCallback != null) {
            statusCallback.onStatus(status.get());
        }
    }

    /**
     * 停止进程监控
     */
    private void stopMonitoring() {
        if (monitorFuture != null) {
            monitorFuture.cancel(true);
            monitorFuture = null;
        }
    }

    /**
     * 停止端口占用进程
     */
    private void stopPortProcess() {
        if (realPid != null) {
            try {
                getPlatForm().killProcess(realPid);
                realPid = null;
            } catch (Exception e) {
                log.error("停止端口占用进程[{}]失败", realPid, e);
            }
        }
    }

    /**
     * 处理进程退出事件
     */
    private void handleProcessExit() {
        ProcessStatus currentStatus = status.get();
        if (currentStatus != ProcessStatus.RUNNING) {
            return;
        }

        stop(false);
        status.set(ProcessStatus.STOPPED);
        log.info("进程已退出: {}", config.getProcessId());

        this.handleProcessFailure();
    }

    /**
     * 处理进程失败
     * <p>
     * 当进程遇到错误或异常终止时，根据配置的重启策略进行处理。
     * 处理过程包括以下步骤：
     * 1. 检查重启策略
     * 2. 根据策略决定是否重启
     * 3. 如果需要重启，执行重启操作
     * </p>
     * <p>
     * 重启策略处理：
     * - NO_RESTART：不进行重启
     * - RESTART_WITH_COUNT：在达到最大重启次数之前进行重启
     * - ALWAYS_RESTART：始终进行重启
     * </p>
     */
    private void handleProcessFailure() {
        if (config.getProcessRestartStrategy() == ProcessRestartStrategy.NO_RESTART) {
            return;
        }

        if (config.getProcessRestartStrategy() == ProcessRestartStrategy.RESTART_WITH_COUNT &&
                restartCount.get() >= config.getMaxRestartCount()) {
            log.warn("进程[{}]已达到最大重启次数[{}]，不再重启", config.getProcessId(), config.getMaxRestartCount());
            this.destroy();
            return;
        }

        restartCount.incrementAndGet();
        restart(false);
    }

    /**
     * 启动输出监控
     * <p>
     * 创建并启动用于监控进程标准输出和错误输出的线程。
     * 监控过程包括以下功能：
     * 1. 实时捕获进程的标准输出
     * 2. 实时捕获进程的错误输出
     * 3. 通过回调函数处理输出内容
     * </p>
     * <p>
     * 监控特性：
     * - 使用独立线程进行非阻塞监控
     * - 支持自定义输出处理回调
     * - 在进程终止时自动停止监控
     * </p>
     */
    private void startOutputMonitor() {
        if (process == null) {
            return;
        }
        if (config.isEnableOutputCallback() && config.getOutputCallback() != null) {
            outputThread = new Thread(() -> {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        config.getOutputCallback().onOutput(config.getProcessId(), line);
                    }
                } catch (IOException e) {
                    if (status.get() != ProcessStatus.STOPPED) {
                        log.error("监控进程[{}]输出时发生错误", config.getProcessId(), e);
                    }
                }
            });
            outputThread.setDaemon(true);
            outputThread.start();
        }

        if (config.isEnableErrorCallback() && config.getErrorCallback() != null) {
            errorThread = new Thread(() -> {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        config.getErrorCallback().onError(config.getProcessId(), line);
                    }
                } catch (IOException e) {
                    if (status.get() != ProcessStatus.STOPPED) {
                        log.error("监控进程[{}]错误输出时发生错误", config.getProcessId(), e);
                    }
                }
            });
            errorThread.setDaemon(true);
            errorThread.start();
        }
    }

    /**
     * 停止输出监控
     * <p>
     * 停止进程的标准输出和错误输出监控线程。
     * 停止过程包括以下步骤：
     * 1. 中断输出监控线程
     * 2. 中断错误输出监控线程
     * 3. 等待线程完全终止
     * </p>
     * <p>
     * 停止策略：
     * - 通过中断信号通知监控线程停止
     * - 等待最多1秒确保线程正常终止
     * - 如果等待超时，继续执行不阻塞
     * </p>
     */
    private void stopOutputMonitor() {
        if (outputThread != null) {
            outputThread.interrupt();
            try {
                outputThread.join(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            outputThread = null;
        }

        if (errorThread != null) {
            errorThread.interrupt();
            try {
                errorThread.join(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            errorThread = null;
        }
    }

    /**
     * 获取进程信息
     *
     * @return 进程信息
     */
    public ProcessInfo getProcessInfo() {
        long pid = getPid();
        double cpuUsage = 0;
        long memoryUsage = 0;

        try {
            PlatformProcessInfo info = getPlatForm().getProcessInfo(pid);
            cpuUsage = info.getCpuUsage();
            memoryUsage = info.getMemoryUsage();
        } catch (Exception e) {
            log.error("获取进程[{}]信息失败", config.getProcessId(), e);
        }

        return ProcessInfo.builder()
                .pid(pid)
                .name(config.getProcessId())
                .status(status.get())
                .cpuUsage(cpuUsage)
                .memoryUsage(memoryUsage)
                .startTime(startTime)
                .uptime(startTime != null ?
                        java.time.Duration.between(startTime, LocalDateTime.now()).toMillis() : 0)
                .port(config.getPort())
                .workingDirectory(config.getWorkingDirectory())
                .commandLine(config.getCommand())
                .restartCount(restartCount.get())
                .lastRestartTime(lastRestartTime.get())
                .build();
    }

    /**
     * 获取当前平台
     *
     * @return 当前平台
     */
    public ProcessPlatform getPlatForm() {
        return config.getPlatformConfig().getCurrentPlatform();
    }

    /**
     * 获取进程ID
     * <p>
     * 获取当前进程的系统进程ID。获取的进程ID并非被监控进程的ID，对于端口被占用的情况返回的是监控端口的进程的ID。如果想始终获取真正运行程序的进程ID，请使用 getRealPid 方法。
     * </p>
     *
     * @return 进程ID，如果无法获取则返回-1
     */
    public long getPid() {
        try {
            return ProcessUtil.getPid(process);
        } catch (Exception e) {
            log.error("获取进程id失败", e);
            return -1;
        }
    }

    /**
     * 获取真正运行程序的进程ID
     *
     * @return 进程ID，如果无法获取则返回-1
     */
    public long getRealPid() {
        if (realPid != null) {
            return realPid;
        }
        return getPid();
    }

    /**
     * 当前进程是否正在运行
     */
    public boolean isRunning() {
        return status.get() == ProcessStatus.RUNNING;
    }

    @PreDestroy
    public void destroy() {
        stop();
        monitorExecutor.shutdownNow();
    }
}
